﻿Namespace Commanding
    Public MustInherit Class AsyncCommand
        Inherits ResultCommand

        Private _BackgroundWorker As New System.ComponentModel.BackgroundWorker

        Private Shared ReadOnly ProgressPercentPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("ProgressPercent", GetType(Integer), GetType(AsyncCommand), New PropertyMetadata())
        Public Shared ReadOnly ProgressPercentProperty As DependencyProperty = ProgressPercentPropertyKey.DependencyProperty
        Public Property ProgressPercent As Integer
            Get
                Return GetValue(ProgressPercentProperty)
            End Get
            Protected Set(ByVal value As Integer)
                SetValue(ProgressPercentPropertyKey, value)
            End Set
        End Property

        Private Shared ReadOnly UserStatePropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("UserState", GetType(Object), GetType(AsyncCommand), New PropertyMetadata())
        Public Shared ReadOnly UserStateProperty As DependencyProperty = UserStatePropertyKey.DependencyProperty
        Public Property UserState As Object
            Get
                Return GetValue(UserStateProperty)
            End Get
            Protected Set(ByVal value As Object)
                SetValue(UserStatePropertyKey, value)
            End Set
        End Property

        Private Shared ReadOnly CancelledPropertyKey As DependencyPropertyKey = DependencyProperty.RegisterReadOnly("Cancelled", GetType(Boolean), GetType(AsyncCommand), New PropertyMetadata())
        Public Shared ReadOnly CancelledProperty As DependencyProperty = CancelledPropertyKey.DependencyProperty
        Public Property Cancelled As Boolean
            Get
                Return GetValue(CancelledProperty)
            End Get
            Private Set(ByVal value As Boolean)
                SetValue(CancelledPropertyKey, value)
            End Set
        End Property


        Public Function CanCancel()
            CanCancel = IsRunning
        End Function

        Public Sub Cancel()
            _BackgroundWorker.CancelAsync()
        End Sub

        Private _DoWorkEventHandler As System.ComponentModel.DoWorkEventHandler

        Protected Overrides Sub OnExecuted(sender As Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
            Exception = Nothing
            Result = Nothing
            Cancelled = False
            IsRunning = True
            Status = WorkingMessage
            _BackgroundWorker.WorkerReportsProgress = True
            _BackgroundWorker.WorkerSupportsCancellation = True

            _DoWorkEventHandler = GetDoWorkHandler()

            AddHandler _BackgroundWorker.ProgressChanged, AddressOf _OnProgressChanged
            AddHandler _BackgroundWorker.RunWorkerCompleted, AddressOf _OnCompleted
            AddHandler _BackgroundWorker.DoWork, _DoWorkEventHandler

            _BackgroundWorker.RunWorkerAsync(e.Parameter)

        End Sub

        Protected MustOverride Function GetDoWorkHandler() As System.ComponentModel.DoWorkEventHandler

        Private Sub _OnProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs)
            OnProgressChanged(sender, e)
        End Sub

        Protected Overridable Sub OnProgressChanged(ByVal sender As Object, ByVal e As System.ComponentModel.ProgressChangedEventArgs)
            ProgressPercent = e.ProgressPercentage
            UserState = e.UserState
        End Sub

        Private Sub _OnCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
            OnCompleted(sender, e)
        End Sub

        Protected Overridable Sub OnCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.RunWorkerCompletedEventArgs)
            RemoveHandler _BackgroundWorker.ProgressChanged, AddressOf _OnProgressChanged
            RemoveHandler _BackgroundWorker.RunWorkerCompleted, AddressOf _OnCompleted
            RemoveHandler _BackgroundWorker.DoWork, _DoWorkEventHandler

            Cancelled = e.Cancelled
            If Not e.Cancelled Then
                If e.Error Is Nothing Then
                    Result = e.Result
                Else
                    Exception = e.Error
                End If
            End If

            _DoWorkEventHandler = Nothing

            Status = ReadyMessage
            IsRunning = False
            CommandManager.InvalidateRequerySuggested()
        End Sub

    End Class

    Public Class BackgroundCommand
        Inherits AsyncCommand

        Public Shared ReadOnly DoWorkHandlerProperty As DependencyProperty = DependencyProperty.Register("DoWorkHandler", GetType(Action(Of Object, System.ComponentModel.DoWorkEventArgs)), GetType(BackgroundCommand))
        Public Property DoWorkHandler As Action(Of Object, System.ComponentModel.DoWorkEventArgs)
            Get
                Return GetValue(DoWorkHandlerProperty)
            End Get
            Set(ByVal value As Action(Of Object, System.ComponentModel.DoWorkEventArgs))
                SetValue(DoWorkHandlerProperty, value)
            End Set
        End Property

        Public Shared ReadOnly CanExecuteFuncProperty As DependencyProperty = DependencyProperty.Register("CanExecuteFunc", GetType(Func(Of Boolean)), GetType(BackgroundCommand))
        Public Property CanExecuteFunc As Func(Of Boolean)
            Get
                Return GetValue(CanExecuteFuncProperty)
            End Get
            Set(ByVal value As Func(Of Boolean))
                SetValue(CanExecuteFuncProperty, value)
            End Set
        End Property

        Protected Overrides ReadOnly Property SupportsCanExecute As Boolean
            Get
                Return True
            End Get
        End Property

        Public Overrides Function CanExecute(ByVal sender As Object, ByVal parameter As Object) As Boolean
            Dim result As Boolean = False
            If (DoWorkHandler IsNot Nothing) Then
                If CanExecuteFunc Is Nothing Then
                    result = True
                Else
                    Try
                        result = CanExecuteFunc.Invoke()
                    Catch ex As Exception
                    End Try
                End If
            End If
            Return result
        End Function

        Protected Overrides Function CreateInstanceCore() As System.Windows.Freezable
            Return New BackgroundCommand
        End Function

        Protected Overrides Function GetDoWorkHandler() As System.ComponentModel.DoWorkEventHandler
            Dim workHandler As New DoWorkCaller(DoWorkHandler)
            Return AddressOf workHandler.DoWork
        End Function

        Private Class DoWorkCaller
            Private _DoWorkHandler As Action(Of Object, System.ComponentModel.DoWorkEventArgs)

            Public Sub New(ByVal doWorkHandler As Action(Of Object, System.ComponentModel.DoWorkEventArgs))
                _DoWorkHandler = doWorkHandler
            End Sub

            Public Sub DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
                _DoWorkHandler.Invoke(sender, e)
            End Sub
        End Class
    End Class
End Namespace
